--  URL-fuse is a circul-breaker for URL
--- 阶段是怎么判断的
local lib = require "demo.fuse.lib"
local log =require "core.log"
local FUSE_END = "fuse_end"
--local FIRST_START = "first_start"
--local LAST_START = "last_start"
local FAIL_COUNT = "fail_count"
local FUSE_TIMES = "fuse_times"
local HALF_OPEN = "half_open"
local BUCKET_ID = "bucket_id"

if ngx.shared.fuse_shard_dict == nil then
    ngx.log(ngx.ERR,"未定义 ngx.shared.fuse_shard_dict ")
else
    print("已经定义 ngx.shared.fuse_shard_dict ")
end



if ngx.shared.properties == nil then
    print("请先定义 ngx.shared.properties ")
else
    print("已经定义 ngx.shared.properties ")
end


-- singleton in one process
local plugin = plugin or {  ---插件的配置
    VERSION = '1.0.0',   ---版本
    dict = ngx.shared.fuse_shard_dict, -- 要用到的缓存
    -- config start
    REQUEST_TIMEOUT = 1, --in seconds   超时时间设置
    FUSED_DURATION = 10, --in seconds   多长时间恢复
    FAILS_LIMIT = 10, --number of consecutive failures  失败个数
    LIFETIME = 15, -- expired counters will be discarded, in seconds  多久会被一期丢掉数据
    DEBUG = true,
    GEN_BUCKET_ID = function(self)   ---生成的桶的id
        return table.concat({ ngx.var.host, ngx.var.uri })
    end,
    ON_DEGRADED_CALLBACK = function(self)  ---降级的回掉函数
        ngx.say("{'code':403,'msg':'熔断了'}")
        ngx.status = 403
        return ngx.exit(403)
    end,
    BEFORE_HALF_OPEN_CALLBACK = function(self)   ----半开的回掉函数
    end,
    AFTER_HALF_OPEN_CALLBACK = function(self)   ----半开后的触发函数
    end,

    VALIDATE_REQUEST_TIMEOUT = function(self)  -----验证请求是否超时 超时返回false
        --true is success
        local elapsed = ngx.now() - ngx.req.start_time()  ---  内置就有开始时间
        return elapsed < self.REQUEST_TIMEOUT
    end,
    -- configure end
}

function plugin:wrap_key(key)   ---- 根据前缀生成这个key
    return table.concat({ ngx.ctx[BUCKET_ID], '@', key })
end

function plugin:incr(key, add, init, expire)  ----自增
    return self.dict:incr(ngx.ctx[key], add, init, expire)   --自带的字典 local dict={}
end

function plugin:get(key)  ---得到key
    return self.dict:get(ngx.ctx[key])
end

function plugin:set(key, val)  --设置值
    return self.dict:set(ngx.ctx[key], val)
end

function plugin:setup(fn_config)  -- 建立初始化
    print(">>>>>>>>>>>>> fuse_plugin setup ")
    fn_config(self)      -----传入的函数
    if self.DEBUG then   ---打印日志
        self:debug("debug is enabled, dict len: ", #self.dict, "  ", " REQUEST_TIMEOUT: ", self.REQUEST_TIMEOUT, " FUSED_DURATION: ", self.FUSED_DURATION, " LIFETIME: ", self.LIFETIME, " FAILS_LIMIT: ", self.FAILS_LIMIT)
    end
end

function plugin:run_access()   ----
---
    log.debug("run_access")
    local ctx = ngx.ctx  ----属于单次请求的上下文
    --need lazy-calculating
    ctx[BUCKET_ID] = self:GEN_BUCKET_ID()  ---桶id
    ctx[FAIL_COUNT] = self:wrap_key(FAIL_COUNT)  ---生成主键  错误个数
    ctx[FUSE_END] = self:wrap_key(FUSE_END) ---生成主键  熔断结束
    ctx[FUSE_TIMES] = self:wrap_key(FUSE_TIMES)---生成主键  熔断次数
    ctx[HALF_OPEN] = self:wrap_key(HALF_OPEN)---生成主键  半开

    if self:is_fused() then
        log.debug("is_fused")
        if self.DEBUG then print('is fused') end
        ctx.fused = 1 ---- 将请求中的标志位 标识熔断 打开
        self:ON_DEGRADED_CALLBACK()  ---触发单次降级
    elseif self:is_half_open() then   ---如果是半开状态
        log.debug("is_half_open")
        if self.DEBUG then print('is half-open') end
        ctx.fused = 1
        self:ON_DEGRADED_CALLBACK()
    elseif self:exiting_fused() then
        log.debug("exiting_fused")
        if self.DEBUG then print('enable half-open, and pass through') end
        ctx.half_open = 1
        self:enable_half_open() -- 如果
        self:BEFORE_HALF_OPEN_CALLBACK()
    end
end

function plugin:run_log()
    log.info("run_log");
    if ngx.ctx.fused == 1 then  --如果断路器开启了的话 直接返回了 不用操作
        if self.DEBUG then
            self:debug("fused, break by_log")
            print('counters ', lib:json(self:counters()))
        end
        return
    end
    -- 如果没有超时
    if self:VALIDATE_REQUEST_TIMEOUT() then  ---没有超时返回true
    log.debug(">>>>>>> log阶段发现没有 超时");

        -- success including half-opening will reset counters
        if self.DEBUG then print('ok, and reset') end
        self:reset_counters()  ---重置请求  关闭半开
    elseif self:is_half_open() then  ---如果在半开状态的话
    log.debug(">>>>>>> log阶段发现是 半开");
        -- failed and half-open  如果是超时并且半开了
        if self.DEBUG then print('in half-open') end
        self:inspect_half_open_request()   ---如果是半开 并且 超时了
        self:AFTER_HALF_OPEN_CALLBACK()   ---
    else  -- 错误次数增加 说明超时了
        log.debug(">>>>>>> log阶段发现是 超时了");
        self:count() --
    end
    if self.DEBUG then
        log.debug('counters ', lib:json(self:counters()))
    end
end

function plugin:fused_times_add()
    if self.DEBUG then print('fused times++') end
    self:reset_counters(true, true)
end

---- 失败次数
function plugin:count()
    self:incr(FAIL_COUNT, 1, 0, self.LIFETIME)
    local fail_count = self:get(FAIL_COUNT)
    if self.DEBUG then print('fail_count: ', fail_count) end
    if fail_count == self.FAILS_LIMIT then
        self:debug("fails reaching the limit ")
        --self:set(LAST_START, ngx.now())
        self:fused_times_add()   --自增加  时间变化  如果
    end
end
----这个函数很有问题 如果是半开中 并且依然超时的话   会调用 并且两个都是true
function plugin:reset_counters(set_fuse_end, incr_fuse_times) --- 这两个都是bool值
    if self.DEBUG then print("reset counters, fuse_end: ", fuse_end, " fuse_times: ", fuse_times) end
    if set_fuse_end == nil then  ---走这里
        log.info("reset_counters :")
        self:set(FUSE_END, fuse_end)  --- TODO fix 这个fuse_end 好奇怪
    else
        self:set(FUSE_END, ngx.now() + self.FUSED_DURATION)  ---
    end
    if incr_fuse_times == nil then   ---走这里
        log.info(" reset_counters fuse_times:")
        self:set(FUSE_TIMES, fuse_times)   ----熔断次数
    else
        self:incr(FUSE_TIMES, 1, 0, 0)   ----增加一次
    end
    self:set(FAIL_COUNT, nil)   -----
    self:set(HALF_OPEN, nil)   -----
end

function plugin:inspect_half_open_request()
    if ngx.ctx.half_open == 1 then  ---如果打开了半开功能
        if self.DEBUG then print("failed in half-open, fused++") end
        self:fused_times_add()
    end
end

function plugin:exiting_fused()
    local fuse_end = self:get(FUSE_END)
    return fuse_end ~= nil and fuse_end < ngx.now()  --如果短路器的结束时间不为空 并且小于当前时间
end

function plugin:is_fused()
    local fuse_end = self:get(FUSE_END)
    return fuse_end ~= nil and fuse_end >= ngx.now()  ---不为空 并且  熔断结束时间大于当前的时间
end

function plugin:is_half_open()   ---是否是半开状态  一般熔断结束后需要经过一个半开状态
    local half_open = self:get(HALF_OPEN)
    return half_open ~= nil and half_open > 0   ------half_open不为空 并且 大于零
end

function plugin:enable_half_open()
    return self:set(HALF_OPEN, 1)
end

function plugin:counters(bucket_all)
    local keys
    if bucket_all == false or bucket_all == nil then
        keys = {
            FUSE_END,
            FAIL_COUNT,
            FUSE_TIMES,
            HALF_OPEN,
        }
    else
        keys = self.dict:get_keys()
    end
    local dict = {}
    for _, v in pairs(keys) do
        dict[v] = self:get(v)
    end
    dict[BUCKET_ID] = self.GEN_BUCKET_ID()
    local free_bytes = self.dict:free_space()
    dict['free_bytes'] = free_bytes
    return dict
end

function plugin:debug(...)
    if self.DEBUG then
        local temp = { ... }
        ngx.log(ngx.INFO, table.concat(temp, " "))
    end
end

return plugin
